using System;
using System.Collections;
using System.Collections.Generic;

namespace LightCAD.Three
{
    public class InstancedMesh : Mesh, IBounding
    {
        #region scope properties or methods
        //private static Matrix4 _instanceLocalMatrix = new Matrix4();
        //private static Matrix4 _instanceWorldMatrix = new Matrix4();
        //private static JsArr< Raycaster.Intersection> _instanceIntersects =new JsArr<Raycaster.Intersection>();

        //private static Mesh _mesh = new Mesh();

        //private static Box3 _box3 = /*@__PURE__*/ new Box3();
        //private static Sphere _sphere = /*@__PURE__*/ new Sphere();
        private static InstancedMeshContext getContext()
            => ThreadContext.getCurrThreadContext().InstancedMeshCtx;
        private static Matrix4 _identity = new Matrix4();
        #endregion

        #region Properties

        public InstancedBufferAttribute instanceMatrix;
        public BufferAttribute instanceColor;
        public Box3 boundingBox;
        public Sphere boundingSphere;
        public int count;

        #endregion

        #region constructor
        public InstancedMesh(BufferGeometry geometry, JsArr<Material> material, int count = 1) : base(geometry, material.ToArray())
        {
            this.instanceMatrix = new InstancedBufferAttribute(new double[count * 16], 16);
            this.instanceColor = null;
            this.count = count;
            this.boundingBox = null;
            this.boundingSphere = null;

            for (int i = 0; i < count; i++)
            {
                this.setMatrixAt(i, _identity);
            }
        }
        #endregion

        #region methods


        public void computeBoundingBox()
        {

            var geometry = this.geometry;
            var count = this.count;

            if (this.boundingBox == null)
            {
                this.boundingBox = new Box3();
            }

            if (geometry.boundingBox == null)
            {
                geometry.computeBoundingBox();
            }

            this.boundingBox.makeEmpty();
            var ctx = getContext();
            var _instanceLocalMatrix = ctx._instanceLocalMatrix;
            var _box3 = ctx._box3;
            for (var i = 0; i < count; i++)
            {
                this.getMatrixAt(i, _instanceLocalMatrix);
                _box3.copy(geometry.boundingBox).applyMatrix4(_instanceLocalMatrix);
                this.boundingBox.union(_box3);
            }
        }

        public void computeBoundingSphere()
        {
            var geometry = this.geometry;
            var count = this.count;

            if (this.boundingSphere == null)
            {
                this.boundingSphere = new Sphere();
            }

            if (geometry.boundingSphere == null)
            {
                geometry.computeBoundingSphere();
            }

            this.boundingSphere.makeEmpty();
            var ctx = getContext();
            var _instanceLocalMatrix = ctx._instanceLocalMatrix;
            var _sphere = ctx._sphere;
            for (var i = 0; i < count; i++)
            {
                this.getMatrixAt(i, _instanceLocalMatrix);
                _sphere.copy(geometry.boundingSphere).applyMatrix4(_instanceLocalMatrix);
                this.boundingSphere.union(_sphere);
            }

        }

        public InstancedMesh copy(InstancedMesh source, bool recursive)
        {
            base.copy(source, recursive);
            this.instanceMatrix.copy(source.instanceMatrix);
            if (source.instanceColor != null)
                this.instanceColor = source.instanceColor.clone();
            this.count = source.count;
            return this;
        }
        public override Object3D copy(Object3D source, bool recursive = true)
        {
            return copy(source as InstancedMesh, recursive);
        }
        public override Object3D clone(bool recursive = true)
        {
            return new InstancedMesh(null, null).copy(this, recursive);
        }
        public void getColorAt(int index, Color color)
        {
            color.fromArray(this.instanceColor.array, index * 3);
        }
        public void getMatrixAt(int index, Matrix4 matrix)
        {
            matrix.fromArray(this.instanceMatrix.array, index * 16);
        }
        public override void raycast(Raycaster raycaster, JsArr<Raycaster.Intersection> intersects = null, object vars = null)
        {
            var matrixWorld = this.matrixWorld;
            var raycastTimes = this.count;
            var ctx = getContext();
            var _mesh = ctx._mesh;
            var _sphere = ctx._sphere;
            var _instanceLocalMatrix = ctx._instanceLocalMatrix;
            var _instanceWorldMatrix = ctx._instanceWorldMatrix;
            var _instanceIntersects = ctx._instanceIntersects;

            _mesh.geometry = this.geometry;
            _mesh.material = this.material;
            if (_mesh.material == null) return;

            // test with bounding sphere first
            if (this.boundingSphere == null) this.computeBoundingSphere();

            _sphere.copy(this.boundingSphere);
            _sphere.applyMatrix4(matrixWorld);

            if (raycaster.ray.intersectsSphere(_sphere) == false) return;

            // now test each instance
            for (int instanceId = 0; instanceId < raycastTimes; instanceId++)
            {
                // calculate the world matrix for each instance
                this.getMatrixAt(instanceId, _instanceLocalMatrix);
                _instanceWorldMatrix.multiplyMatrices(matrixWorld, _instanceLocalMatrix);
                // the mesh represents this single instance
                _mesh.matrixWorld = _instanceWorldMatrix;
                _mesh.raycast(raycaster, _instanceIntersects);
                // process the result of raycast
                for (int i = 0, l = _instanceIntersects.length; i < l; i++)
                {
                    var intersect = _instanceIntersects[i];
                    intersect.instanceId = instanceId;
                    intersect.target = this;
                    intersects.push(intersect);
                }
                _instanceIntersects.Clear();
            }
        }
        public void setColorAt(int index, Color color)
        {
            if (this.instanceColor == null)
            {
                this.instanceColor = new InstancedBufferAttribute(new double[this.instanceMatrix.count * 3], 3);
            }
            color.toArray(this.instanceColor.array, index * 3);
        }
        public void setMatrixAt(int index, Matrix4 matrix)
        {
            matrix.toArray(this.instanceMatrix.array, index * 16);
        }
        public void updateMorphTargets()
        {
        }
        public void dispose()
        {
            this.dispatchEvent(new EventArgs { type = "dispose" });
        }
        #endregion

    }
}
